package com.wa.base.dataPermission;

import com.wa.page.Page;
import com.wa.page.PageInterceptor;
import com.wa.util.CommUtils;
import com.wa.util.JsonUtil;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 数据权限处理--MyBatis拦截器
 *
 * @author Homan
 */
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),})
@Component
public class DataPermissionInterceptor implements Interceptor {
    /**
     * 日志
     */
    private static final Logger logger = LoggerFactory.getLogger(PageInterceptor.class);

    /**
     * 数据权限开关，true:启用，false:禁用
     */
    @Value("${data.permission.switch:false}")
    private Boolean dataPermissionSwitch;

    /**
     * 这是对应上面的args的序号
     */
    private final static int MAPPED_STATEMENT_INDEX = 0;

    /**
     * 拦截后处理
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1.数据权限开关
        if (dataPermissionSwitch == null || (!dataPermissionSwitch)) {
            return invocation.proceed();
        }
        // 2.只处理 Executor 类型
        Object target = invocation.getTarget();
        if (!(target instanceof Executor)) {
            return invocation.proceed();
        }
        Object[] args = invocation.getArgs();
        if (args.length <= 0) {
            return invocation.proceed();
        }
        MappedStatement mappedStatement = (MappedStatement) args[0];
        // 3.支持处理查询语句
        boolean isUpdate = (args.length == 2);
        if (isUpdate || (mappedStatement.getSqlCommandType() != SqlCommandType.SELECT)) {
            return invocation.proceed();
        }
        // 4.获取分页处理参数
        Object parameter = args[1];
        Page page = null;
        // 先取Page
        Optional<Page> pageOptional = this.findClass(parameter, Page.class);
        if (pageOptional.isPresent()) {
            page = pageOptional.get();
        }
        // 再取DataPermissionEntity
        DataPermissionEntity dataPermissionEntity = null;
        if (page == null) {
            Optional<DataPermissionEntity> dataPermissionEntityOptional = this.findClass(parameter, DataPermissionEntity.class);
            if (dataPermissionEntityOptional.isPresent()) {
                dataPermissionEntity = dataPermissionEntityOptional.get();
            }
        }
        // 最后取DpLambdaQueryWrapper
        DpLambdaQueryWrapper dpLambdaQueryWrapper = null;
        if (page == null && dataPermissionEntity == null) {
            Optional<DpLambdaQueryWrapper> dpLambdaQueryWrapperOptional = this.findClass(parameter, DpLambdaQueryWrapper.class);
            if (dpLambdaQueryWrapperOptional.isPresent()) {
                dpLambdaQueryWrapper = dpLambdaQueryWrapperOptional.get();
            }
        }
        // 取不到数据权限参数，就返回
        if (page == null && dataPermissionEntity == null && dpLambdaQueryWrapper == null) {
            logger.info("数据权限SQL转换==不需要转换");
            return invocation.proceed();
        }
        // 5.获取SQL
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        if (CommUtils.isNull(boundSql) || CommUtils.isEmpty(boundSql.getSql())) {
            logger.info("数据权限SQL转换==空SQL");
            return invocation.proceed();
        }
        // 6.数据权限SQL转换
        String sql = this.transferDataPermission(boundSql.getSql(), page, dataPermissionEntity, dpLambdaQueryWrapper);
        if (CommUtils.isEmpty(sql)) {
            logger.info("数据权限SQL转换==转换成空SQL");
            return invocation.proceed();
        }
        if (boundSql.getSql().equals(sql)) {
            logger.info("数据权限SQL转换==不需要处理SQL");
            return invocation.proceed();
        }
        // 7.重新new一个查询语句对像，把新的查询放到statement里
        BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), sql, boundSql.getParameterMappings()
                , boundSql.getParameterObject());
        MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
        for (ParameterMapping mapping : boundSql.getParameterMappings()) {
            String property = mapping.getProperty();
            if (boundSql.hasAdditionalParameter(property)) {
                newBoundSql.setAdditionalParameter(property, boundSql.getAdditionalParameter(property));
            }
        }
        args[MAPPED_STATEMENT_INDEX] = newMs;

        return invocation.proceed();
    }

    /**
     * 复制MappedStatement
     *
     * @param ms
     * @param newSqlSource
     * @return
     */
    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    /**
     * BoundSqlSqlSource
     */
    public static class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }

    /**
     * 找数据权限参数
     *
     * @param parameter
     * @param tClass
     * @return
     */
    public <T> Optional<T> findClass(Object parameter, Class<T> tClass) {
        if (parameter == null) {
            return Optional.empty();
        }
        if (parameter instanceof Map) {
            Map<?, ?> parameterMap = (Map<?, ?>) parameter;
            for (Map.Entry entry : parameterMap.entrySet()) {
                if (entry.getValue() == null) {
                    continue;
                }
                if (entry.getValue().getClass().equals(tClass)) {
                    return Optional.of((T) entry.getValue());
                }
                if (entry.getValue().getClass().getSuperclass() != null && entry.getValue().getClass().getSuperclass().equals(tClass)) {
                    return Optional.of((T) entry.getValue());
                }
            }
        } else if (parameter.getClass().equals(tClass)) {
            return Optional.of((T) parameter);
        }
        return Optional.empty();
    }

    /**
     * 拦截器对应的封装原始对象的方法
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 数据权限SQL转换
     *
     * @param sql
     * @param page
     * @param dataPermissionEntity
     * @param dpLambdaQueryWrapper
     * @return
     * @throws Exception
     */
    public String transferDataPermission(String sql, Page page, DataPermissionEntity dataPermissionEntity
            , DpLambdaQueryWrapper dpLambdaQueryWrapper) throws Exception {
        // 数据权限开关
        if (CommUtils.isNull(dataPermissionSwitch) || (!dataPermissionSwitch)) {
            return sql;
        }

        logger.info("<======== 数据权限SQL转换==开始 ============>");

        logger.info("数据权限SQL转换==转换前的SQL:" + sql);

        try {
            if (CommUtils.isNotNull(page)) {
                // 打印数据权限参数
                this.printSqlParam(page.getParam());
                // 数据权限 SQL 转换
                sql = this.transferDataPermission(page.getExtraParam(), sql);

                logger.info("<======== 数据权限SQL转换==成功==page ============>");
            } else if (CommUtils.isNotNull(dataPermissionEntity)) {
                // 打印数据权限参数
                this.printSqlParam(dataPermissionEntity);
                // 数据权限 SQL 转换
                sql = this.transferDataPermission(dataPermissionEntity.getExtraParam(), sql);

                logger.info("<======== 数据权限SQL转换==成功==dataPermissionEntity ============>");
            } else if (CommUtils.isNotNull(dpLambdaQueryWrapper)) {
                // 打印数据权限参数
                this.printSqlParam(dpLambdaQueryWrapper);
                // 数据权限 SQL 转换
                sql = this.transferDataPermission(dpLambdaQueryWrapper.getExtraParam(), sql);

                logger.info("<======== 数据权限SQL转换==成功==dpLambdaQueryWrapper ============>");
            } else {
                logger.info("<======== 数据权限SQL转换==不是数据权限参数类型 ============>");
                return sql;
            }
        } catch (Exception e) {
            logger.error("数据权限SQL转换==异常：", e);
            throw new Exception("数据权限SQL转换==异常：" + e.toString());
        } catch (Throwable t) {
            logger.error("数据权限SQL转换==异常Throwable：", t);
            throw new Exception("数据权限SQL转换==异常Throwable：" + t.toString());
        }
        return sql;
    }

    /**
     * 数据权限 SQL 转换
     *
     * @param extraParamMap 数据权限参数
     * @param sql           SQL
     * @return
     * @throws Exception
     */
    private String transferDataPermission(Map<String, Object> extraParamMap, String sql) throws Exception {
        // 获取额外参数，如果没有额外参数就返回
        if (CommUtils.isNull(extraParamMap) || extraParamMap.size() <= 0) {
            return sql;
        }
        // 获取数据权限参数，如果没有就返回
        Object dataPermissionParamObj = extraParamMap.get(DataPermissionConstant.MENU_ROLE_DATA_PERMISSION_RELATION);
        if (CommUtils.isNull(dataPermissionParamObj)) {
            return sql;
        }
        if (!(dataPermissionParamObj instanceof Map)) {
            return sql;
        }
        Map<String, Object> dataPermissionParamMap = (Map<String, Object>) dataPermissionParamObj;

        logger.info("数据权限SQL转换==规则参数:" + JsonUtil.toJson(dataPermissionParamMap));
        // 数据权限规则
        Object queryFilterGroupObj = dataPermissionParamMap.get(DataPermissionConstant.DATA_PERMISSION_ROW);
        Map<String, List<QueryFilterRuleTree>> queryFilterGroupMap = new HashMap<>(16);
        if (CommUtils.isNotNull(queryFilterGroupObj) && queryFilterGroupObj instanceof Map) {
            queryFilterGroupMap = (Map<String, List<QueryFilterRuleTree>>) queryFilterGroupObj;
        }
        if (CommUtils.isNull(queryFilterGroupMap) || queryFilterGroupMap.size() <= 0) {
            // 数据权限SQL转换,如果没有设置数据权限，并且配置默认不查询所有数据，就不允许查询数据
            sql = this.transferDataPermissionNotShow(sql, dataPermissionParamMap);

            return sql;
        }
        // 数据权限变量
        Object paramObj = dataPermissionParamMap.get(DataPermissionConstant.DATA_PERMISSION_ROW_VARIABLE);
        Map<String, String> paramMap = new HashMap<>(16);
        if (CommUtils.isNotNull(paramObj) && paramMap instanceof Map) {
            paramMap = (Map<String, String>) paramObj;
        }
        // SQL 转换处理
        logger.info("数据权限SQL转换==SQL 转换处理");
        sql = QueryFilterSqlSplicerUtil.spliceSql(sql, queryFilterGroupMap, paramMap);

        logger.info("数据权限SQL转换==转换后的SQL:" + sql);

        return sql;
    }

    /**
     * 打印 SQL 参数（数据权限）
     *
     * @param param
     */
    private void printSqlParam(Object param) {
        if (CommUtils.isNull(param)) {
            logger.info("数据权限SQL转换==SQL参数：无");
            return;
        }

        String sqlParam = JsonUtil.toJson(param);
        if (CommUtils.isEmpty(sqlParam)) {
            sqlParam = "";
        }
        int length = 1000;
        if (sqlParam.length() > length) {
            sqlParam = sqlParam.substring(0, length);
        }
        logger.info("数据权限SQL转换==SQL参数：" + sqlParam);
    }

    /**
     * 数据权限SQL转换,如果没有设置数据权限，并且配置默认不查询所有数据，就不允许查询数据
     *
     * @param sql
     * @param dataPermissionParamMap
     * @return
     */
    private String transferDataPermissionNotShow(String sql, Map<String, Object> dataPermissionParamMap) {
        logger.info("数据权限SQL转换==不允许查询数据==转换前sql：" + sql);
        Object defaultShowAll = dataPermissionParamMap.get(DataPermissionConstant.DATA_PERMISSION_DEFAULT_SHOW_ALL);

        boolean isNotShowAll = false;

        try {
            isNotShowAll = CommUtils.isNotNull(defaultShowAll)
                    && (defaultShowAll instanceof Boolean)
                    && (!(boolean) defaultShowAll);
        } catch (Exception e) {
            logger.error("数据权限SQL转换==获取“是否查询所有数据”失败：", e);
        }

        if (isNotShowAll) {
            sql = "select * from ( " + sql + ") a where 1=2";
            logger.info("数据权限SQL转换==不允许查询数据==转换后sql：" + sql);
        }
        return sql;
    }
}
